// gcc multi_download.c -lcurl -lpthread
// ./a.out http://speedtest.tele2.net/10MB.zip 10MB.zip

#include <stdio.h>
#include <unistd.h>
#include <curl/curl.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>

// fork();

struct fileInfo
{
    const char *url;
    char *fileptr;
    int offset; //  start
    int end;    // end
    pthread_t thid;
    double download; //
    double totalDownload;
    FILE *recordfile;
};

#define THREAD_NUM 10

struct fileInfo **pInfoTable;
double downloadFileLength = 0;

// fwrite(fp, size, count, buf);
// libcurl

// 1.  fwrite/write
// mmap

size_t writeFunc(void *ptr, size_t size, size_t memb, void *userdata)
{
    // --> ptr
    struct fileInfo *info = (struct fileInfo *)userdata;
    // printf("writeFunc: %ld\n", size * memb);

    memcpy(info->fileptr + info->offset, ptr, size * memb);
    info->offset += size * memb;

    return size * memb;
}

//
int progressFunc(void *userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload)
{

    int percent = 0;
    static int print = 1;
    struct fileInfo *info = (struct fileInfo *)userdata;
    info->download = nowDownload;
    info->totalDownload = totalDownload;
    // save

    if (totalDownload > 0)
    {

        int i = 0;
        double allDownload = 0;
        double total = 0;

        for (i = 0; i <= THREAD_NUM; i++)
        {
            allDownload += pInfoTable[i]->download;
            total += pInfoTable[i]->totalDownload;
        }

        percent = (int)(allDownload / total * 100);
    }

    if (percent == print)
    {
        printf("threadid: %ld, percent: %d%%\n", info->thid, percent);
        print += 1;
    }

    return 0;
}

//
double getDownloadFileLength(const char *url)
{

    CURL *curl = curl_easy_init();

    // printf("url: %s\n", url);
    curl_easy_setopt(curl, CURLOPT_URL, url);
    // curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36");
    curl_easy_setopt(curl, CURLOPT_HEADER, 1);
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1);

    // 111
    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK)
    {
        printf("downloadFileLength success\n");
        curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
    }
    else
    {
        printf("downloadFileLength error\n");
        downloadFileLength = -1;
    }
    // 2222
    curl_easy_cleanup(curl);

    return downloadFileLength;
}

int recordnum = 0;
// curl
// 0 - 11
// multi
void *worker(void *arg)
{

    struct fileInfo *info = (struct fileInfo *)arg;

    char range[64] = {0};

    // mutex_lock
    if (info->recordfile)
    {
        fscanf(info->recordfile, "%d-%d", &info->offset, &info->end);
    }
    // mutex_unlock
    if (info->offset > info->end)
        return NULL;

    snprintf(range, 64, "%d-%d", info->offset, info->end);

    // printf("threadid: %ld, download from: %d to: %d\n", info->thid, info->offset, info->end);
    //  curl -->
    CURL *curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, info->url); // url

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); // save
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, info);

    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // progress
    curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressFunc);
    curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, info);

    curl_easy_setopt(curl, CURLOPT_RANGE, range);

    // http range
    // 111
    // multicurl
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK)
    {
        printf("res %d\n", res);
    }
    // 2222
    curl_easy_cleanup(curl);

    return NULL;
}

// https://releases.ubuntu.com/22.04/ubuntu-22.04.2-live-server-amd64.iso.zsync
// ubuntu.zsync.backup
int download(const char *url, const char *filename)
{
    //
    long fileLength = getDownloadFileLength(url);
    printf("downloadFileLength: %ld\n", fileLength);

    // write
    int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); //
    if (fd == -1)
    {
        return -1;
    }

    if (-1 == lseek(fd, fileLength - 1, SEEK_SET))
    {
        perror("lseek");
        close(fd);
        return -1;
    }
    if (1 != write(fd, "", 1))
    {
        perror("write");
        close(fd);
        return -1;
    }

    char *fileptr = (char *)mmap(NULL, fileLength, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fileptr == MAP_FAILED)
    {
        perror("mmap");
        close(fd);
        return -1;
    }
    // fileLength 2014, 11

    FILE *fp = fopen("a.txt", "r");

    // 0 99
    // 100 199
    // 200 299
    // 300 399
    // .....
    // 1000 1023
    // thread arg
    int i = 0;
    long partSize = fileLength / THREAD_NUM;
    struct fileInfo *info[THREAD_NUM + 1] = {NULL};

    for (i = 0; i <= THREAD_NUM; i++)
    {

        info[i] = (struct fileInfo *)malloc(sizeof(struct fileInfo));
        memset(info[i], 0, sizeof(struct fileInfo));

        info[i]->offset = i * partSize;
        if (i < THREAD_NUM)
        {
            info[i]->end = (i + 1) * partSize - 1;
        }
        else
        {
            info[i]->end = fileLength - 1;
        }
        info[i]->fileptr = fileptr;
        info[i]->url = url;
        info[i]->download = 0;
        info[i]->recordfile = fp;
    }
    pInfoTable = info;

    // pthread_t thid[THREAD_NUM+1] = {0};
    for (i = 0; i <= THREAD_NUM; i++)
    {
        pthread_create(&(info[i]->thid), NULL, worker, info[i]);
        usleep(1);
    }

    for (i = 0; i <= THREAD_NUM; i++)
    {
        pthread_join(info[i]->thid, NULL);
    }

    for (i = 0; i <= THREAD_NUM; i++)
    {
        free(info[i]);
    }

    if (fp)
        fclose(fp);

    munmap(fileptr, fileLength);
    close(fd);

    return 0;
}

//
void signal_handler(int signum)
{

    printf("signum: %d\n", signum);

    // unlink("a.txt");
    // save -->
    int fd = open("a.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1)
    {
        exit(1);
    }

    int i = 0;
    for (i = 0; i <= THREAD_NUM; i++)
    {
        char range[64] = {0};
        snprintf(range, 64, "%d-%d\r\n", pInfoTable[i]->offset, pInfoTable[i]->end);
        write(fd, range, strlen(range));
    }

    close(fd);

    exit(1);
}

// multi_download  https://releases.ubuntu.com/22.04/ubuntu-22.04.2-live-server-amd64.iso.zsync  ubuntu.zsync
int main(int argc, const char *argv[])
{
    if (argc != 3)
    {
        printf("arg error\n");
        return -1;
    }

    if (SIG_ERR == signal(SIGINT, signal_handler))
    {
        perror("signal");
        return -1;
    }

    return download(argv[1], argv[2]);
}
